const mkdirp = require('mkdirp');
const fs = require('fs');
const Web3 = require('web3');
const CLUI = require('clui');
const Gauge = CLUI.Gauge;
const Spinner = CLUI.Spinner;
const mock = require('./mockRndTxs.js');
const batch = process.argv[3] || 0;
const step = process.argv[4] || 10;
const rpc = process.argv[5];
const output = process.argv[6];

const TEST_AMOUNT = 100;
const BLOCK_AMOUNT = 1;
const TX_RATE = 1000;

var spinner = new Spinner('running benchmarking...\t', ['◜','◠','◝','◞','◡','◟']);
var mocking = mock({batch, step, rpc});
var data = [];

var executor = (resolve, reject) => {
  spinner.start();
  var perbatch = 0;
  var blockCount = 0;
  var totalTps = 0;
  var maxTps = 0;
  var spends = 0;
  var offset = 15;
  var lastTimestamp = 0;
  var web3 = new Web3(new Web3.providers.HttpProvider(rpc)); // always make a new web3 for every promise
  [this.unmock, perbatch] = mocking();
  this.filter = web3.eth.filter('latest');
  this.filter.watch((error, blockhash) => {
    if (error) {
      reject('Error on getting latest mined block: ' + error);
    }
    var occured = new Date().getTime();
    web3.eth['getBlock'](blockhash, (error, block) => {
      spends += (new Date().getTime() - occured);
      if (error) {
        reject('Error receiving mined block: ' + error);
      }
      if (lastTimestamp && lastTimestamp < block.timestamp) {
        offset = block.timestamp - lastTimestamp;
      }
      lastTimestamp = block.timestamp;
      var statusChecks = [];
      for (var i = 0; i < block.transactions.length; i += TX_RATE) {
        statusChecks.push(new Promise((resolve, reject) => {
          var txReceipt = web3.eth['getTransactionReceipt'](block.transactions[i]);
          if (typeof txReceipt == 'undefined') {
            reject('undefined transaction in block');
          }
          resolve(true);
        }).catch(reject));
      }
      Promise.all(statusChecks).then(() => {
        var tps = block.transactions.length / offset;
        if (tps > 700) {
          console.log('tx len, offset: ', block.transactions.length, offset)
        }
        totalTps += tps;
        blockCount += 1;
        if (maxTps < tps) {
          maxTps = tps;
        }
        if (blockCount >= BLOCK_AMOUNT) {
          this.gauge = Gauge(tps, 1000, 20, 800, (totalTps / blockCount).toFixed(2) + ' tps, perbatch: ' + perbatch);
          resolve({
            batch: perbatch,
            maxTps: Math.round(maxTps),
            avgTps: Math.round(totalTps / blockCount),
            speed: Math.round(spends / blockCount),
          });
        }
      });
    });
  });
}

var run = () => new Promise(executor)
  .then(data.push.bind(data))
  .catch(console.error)
  .finally(() => {
    this.unmock();
    this.filter.stopWatching();
    spinner.stop();
    console.log(this.gauge);
    if (data.length < TEST_AMOUNT) {
      run();
    } else {
      mkdirp.sync('./out');
      data = 'export default ' + JSON.stringify(data) + ';';
      fs.writeFileSync('./out/' + (output || 'perf-data.js'), data);
      spinner.stop();
    }
  });

run();
